import datetime
import time
from datetime import datetime, timedelta

# 定额计算
from src.database.connection import Neo4jInstance
from src.util.applog import AppLog


graph = Neo4jInstance.get_connect()

WORKER = "Worker"
WORKLOAD = "Workload"
WORK_DURATION = "WorkDuration"

WORK_START_DATE = "WorkStartDate"
WORK_END_DATE = "WorkEndDate"


def clean_data():
    # 删除旧数据
    graph.run(
        """
        MATCH (n)
        REMOVE n.WorkStartDate
        RETURN count(n) as AffectedNodes
        """
    )
    graph.run(
        """
        MATCH (n)
        REMOVE n.WorkEndDate
        RETURN count(n) as AffectedNodes
        """
    )


def calc_project_date(project_date):
    clean_data()

    # 查询所有的building
    wait_task_dict = {}
    buildings = graph.run(
        """
        MATCH (c:IfcBuilding) RETURN c
    """
    ).data()
    buildings = [building["c"] for building in buildings]
    # current_time = datetime.datetime.now()
    # end_time = current_time + datetime.timedelta(minutes=30)  # 计算最长时间，30分钟
    for building in buildings:
        building[WORK_START_DATE] = project_date
        graph.push(building)
        find_work_item(building, project_date)


def find_work_item(item_node, project_date):
    current_node = item_node
    while True:
        if current_node is None:
            # 末端节点，会没有下一个关联的节点，进入这里，需要继续从未完成计算的节点重新开始
            # 例如  B->A<-C ，如果 A 到了 B，那么就不会计算C
            children = graph.run(
                """
            MATCH (n)
            WHERE n.WorkEndDate IS NULL
            RETURN n LIMIT 1
            """,
                id=item_node.identity,
            ).data()
            AppLog.info(f" ********************** 未完成计算节点数： {len(children)} ")
            if len(children) == 0:
                break
            else:
                current_node = children[0]["n"]
                continue

        # 测试使用
        AppLog.info(
            f"  Name: {current_node['Name']} ,"
            f"  Label: {list(current_node.labels)[0]} ,"
            f"  GlobalId: {current_node['GlobalId']}  ,"
            f"  NEO4J ID : {current_node.identity}"
        )

        if current_node.identity == 36945:
            pass

        if list(current_node.labels)[0] == "IfcBuilding":
            current_node[WORK_START_DATE] = current_node[WORK_START_DATE]
            graph.push(current_node)
            child_node, end_date = get_child_node_for_end_date(current_node)
            if child_node is None:
                current_node[WORK_END_DATE] = end_date
                graph.push(current_node)
                return
            else:
                current_node = child_node

        else:
            parent_node, start_date = get_parent_node_for_start_date(current_node)
            if parent_node is not None:
                current_node = parent_node
                continue
            else:
                # 没有父级节点 或 父级节点已计算完成
                if start_date is None:
                    # 虚拟节点，可能没有依赖
                    start_date = project_date
                current_node[WORK_START_DATE] = start_date
                graph.push(current_node)
                if WORK_DURATION in dict(current_node):
                    current_node[WORK_END_DATE] = current_node[
                        WORK_START_DATE
                    ] + timedelta(days=current_node[WORK_DURATION])
                    graph.push(current_node)
                    # 计算完成，寻找依赖当前节点的任务
                    current_node = get_followed_node(current_node)
                    continue
                else:
                    # # 虚拟节点，没有工作周期，开始结束时间，都是父级节点的结束时间
                    # current_node[WORK_END_DATE] = current_node[WORK_START_DATE]
                    child_node, end_date = get_child_node_for_end_date(current_node)
                    if child_node is None:
                        # 虚拟节点，可能会没有子节点
                        # 一个子节点没有工作时长，也没有子节点，那么开始时间和结束时间相同
                        if end_date is None:
                            current_node[WORK_END_DATE] = current_node[WORK_START_DATE]
                        else:
                            current_node[WORK_END_DATE] = end_date
                        graph.push(current_node)
                        # 一个构件的任务完成(每个子 task)，寻找依赖当前节点的其他节点，继续计算
                        current_node = get_followed_node(current_node)
                        continue
                    else:
                        # 有未计算的子节点
                        current_node = child_node
                        continue


# 查找子任务
def get_child_node_for_end_date(item_node):
    child_end_date = None
    # FOR 关系
    for_child_nodes = graph.run(
        """
        MATCH (c)-[:FOR]->(p)
        WHERE id(p) = $parent_id
        RETURN c
    """,
        parent_id=item_node.identity,
    ).data()
    if len(for_child_nodes) > 0:
        if child_end_date is None:
            child_end_date = for_child_nodes[0]["c"][WORK_END_DATE]
        for for_child_node in for_child_nodes:
            if WORK_END_DATE not in for_child_node["c"]:
                return for_child_node["c"], None
            child_end_date = max(child_end_date, for_child_node["c"][WORK_END_DATE])

    # CONTAINS 关系
    contains_child_nodes = graph.run(
        """
        MATCH (c)<-[:CONTAINS]-(p)
        WHERE id(p) = $parent_id
        RETURN c
    """,
        parent_id=item_node.identity,
    ).data()
    if len(contains_child_nodes) > 0:
        if child_end_date is None:
            child_end_date = contains_child_nodes[0]["c"][WORK_END_DATE]
        for contains_child_node in contains_child_nodes:
            if WORK_END_DATE not in contains_child_node["c"]:
                return contains_child_node["c"], None
            child_end_date = max(
                child_end_date, contains_child_node["c"][WORK_END_DATE]
            )

    # PLACED_ID 关系
    placed_child_nodes = graph.run(
        """
        MATCH (c)-[:PLACED_IN]->(p)
        WHERE id(p) = $parent_id
        RETURN c
    """,
        parent_id=item_node.identity,
    ).data()

    if len(placed_child_nodes) > 0:
        if child_end_date is None:
            child_end_date = placed_child_nodes[0]["c"][WORK_END_DATE]
        for placed_child_node in placed_child_nodes:
            if WORK_END_DATE not in placed_child_node["c"]:
                return placed_child_node["c"], None
            child_end_date = max(child_end_date, placed_child_node["c"][WORK_END_DATE])
    return None, child_end_date


# 当前节点的上级节点，存在4种情况：
# 1、FOR 关系
# 2、CONTAINS 关系
# 3、FOLLOW 关系
# 4、PLACED_IN
# 取上级未来完成的节点返回
def get_parent_node_for_start_date(item_node):
    parent_start_date = None
    # 1、FOR 关系 取前置任务的开始时间作为开始时间
    for_parent_nodes = graph.run(
        """
        MATCH (c)-[:FOR]->(p)
        WHERE id(c) = $child_id
        RETURN p
    """,
        child_id=item_node.identity,
    ).data()
    if len(for_parent_nodes) > 0:
        if parent_start_date is None:
            parent_start_date = for_parent_nodes[0]["p"][WORK_START_DATE]
        for contain_parent_node in for_parent_nodes:
            if WORK_START_DATE not in contain_parent_node["p"]:
                return contain_parent_node["p"], None
            parent_start_date = min(
                parent_start_date, contain_parent_node["p"][WORK_START_DATE]
            )
    # 2、CONTAINS 关系 取前置任务的开始时间作为开始时间
    contain_parent_nodes = graph.run(
        """
        MATCH (c)<-[:CONTAINS]-(p)
        WHERE id(c) = $child_id
        RETURN p
    """,
        child_id=item_node.identity,
    ).data()
    if len(contain_parent_nodes) > 0:
        if parent_start_date is None:
            parent_start_date = contain_parent_nodes[0]["p"][WORK_START_DATE]
        for contain_parent_node in contain_parent_nodes:
            if WORK_START_DATE not in contain_parent_node["p"]:
                return contain_parent_node["p"], None
            parent_start_date = min(
                parent_start_date, contain_parent_node["p"][WORK_START_DATE]
            )
    # 3、FOLLOW 关系 取前置任务的最后时间作为开始时间
    follow_parent_nodes = graph.run(
        """
        MATCH (c)-[:FOLLOW]->(p)
        WHERE id(c) = $child_id
        RETURN p
        """,
        child_id=item_node.identity,
    ).data()
    if len(follow_parent_nodes) > 0:
        if parent_start_date is None:
            parent_start_date = follow_parent_nodes[0]["p"][WORK_END_DATE]
        for follow_parent_node in follow_parent_nodes:
            if WORK_END_DATE not in dict(follow_parent_node["p"]):
                return follow_parent_node["p"], None
            parent_start_date = max(
                parent_start_date, follow_parent_node["p"][WORK_END_DATE]
            )
    # 4、PLACED_ID 关系 取前置任务的开始时间作为开始时间
    placed_parent_nodes = graph.run(
        """
        MATCH (c)-[:PLACED_IN]->(p)
        WHERE id(c) = $child_id
        RETURN p
    """,
        child_id=item_node.identity,
    ).data()
    if len(placed_parent_nodes) > 0:
        if parent_start_date is None:
            parent_start_date = placed_parent_nodes[0]["p"][WORK_START_DATE]
        for placed_parent_node in placed_parent_nodes:
            if WORK_START_DATE not in placed_parent_node["p"]:
                return placed_parent_node["p"], None
            parent_start_date = min(
                parent_start_date, placed_parent_node["p"][WORK_START_DATE]
            )
    return None, parent_start_date


# 获取当前节点被依赖的节点，用于获取未计算的节点
def get_followed_node(item_node):
    # FOLLOW 关系
    follow_child_nodes = graph.run(
        """
        MATCH (c)-[:FOLLOW]->(p)
        WHERE id(p) = $parent_id
        RETURN c
        """,
        parent_id=item_node.identity,
    ).data()
    if len(follow_child_nodes) > 0:
        for follow_child_node in follow_child_nodes:
            if WORK_START_DATE not in follow_child_node:
                return follow_child_node["c"]
    return None


############################
# 获取前置任务节点，未计算的节点
# 如果前置任务都完成，返回前置任务的最后时间
def get_waiting_node(nodes):
    waiting_node = None
    end_date = None
    if len(nodes) > 0:
        end_date = nodes[0][WORK_END_DATE]
        for node in nodes:
            component = node
            if WORK_END_DATE not in dict(component):
                waiting_node = component
                return waiting_node, end_date
            end_date = max(end_date, component[WORK_END_DATE])
    return waiting_node, end_date


def find_work_item2(item_node):
    current_node = item_node
    while True:
        AppLog.info(
            f"---- Name: {current_node['Name']}  ,"
            f"  Label: {list(current_node.labels)[0]} ,"
            f"  GlobalId: {current_node['GlobalId']}  ,"
            f"  NEO4J ID : {current_node.identity}"
        )
        if current_node is None:
            break
        if list(current_node.labels)[0] == "IfcBuilding":
            components = graph.run(
                """
            MATCH (c)<-[:CONTAINS]-(s)
            WHERE id(s) = $s_id
            RETURN c
            """,
                s_id=current_node.identity,
            ).data()
            components = [component["c"] for component in components]
            is_finished = True
            end_date = current_node[WORK_START_DATE]
            for component in components:
                if WORK_END_DATE not in dict(component):
                    component[WORK_START_DATE] = current_node[WORK_START_DATE]
                    graph.push(component)
                    current_node = component
                    is_finished = False
                    break
                end_date = max(end_date, component[WORK_END_DATE])
            if is_finished:
                # 每个楼层都已经完工了，可以结束
                current_node[WORK_END_DATE] = end_date
                break
            else:
                continue
        elif list(current_node.labels)[0] == "IfcBuildingStorey":
            components = graph.run(
                """
            MATCH (c)-[:FOLLOW]->(p)
            WHERE id(c) = $child_id
            RETURN p
            """,
                child_id=current_node.identity,
            )
            components = [component["p"] for component in components]
            waiting_node, end_date = get_waiting_node(components)
            if waiting_node is not None:
                current_node = waiting_node
                continue
            components = graph.run(
                """
            MATCH (c)-[:PLACED_IN]->(p)
            WHERE id(p) = $parent_id
            RETURN c
            """,
                parent_id=current_node.identity,
            ).data()
            if len(components) > 0:
                components = [component["c"] for component in components]
                is_finished = True
                work_end_date = current_node[WORK_START_DATE]
                for component in components:
                    if WORK_END_DATE not in dict(component):
                        is_finished = False
                        current_node = components[0]
                        break
                    work_end_date = max(work_end_date, component[WORK_END_DATE])
                if is_finished:
                    current_node[WORK_END_DATE] = work_end_date
                    graph.push(current_node)
                    graph.run(
                        """
                        MATCH (c)-[:FOLLOW]->(p)
                        WHERE id(c) = $child_id AND c.work_end_date IS NOT NULL
                        RETURN C
                    """
                    )
                else:
                    continue
            continue
        elif list(current_node.labels)[0].find("Ifc") != -1:
            components = graph.run(
                """
            MATCH (c)-[:FOR]->(p)
            WHERE id(p) = $parent_id
            RETURN c
            """,
                child_id=current_node.identity,
            ).data()
            components = [component["c"] for component in components]
            is_finished = True
            end_date = current_node[WORK_START_DATE]
            if len(components) > 0:
                for component in components:
                    if WORK_END_DATE not in dict(component):
                        component[WORK_START_DATE] = current_node[WORK_START_DATE]
                        current_node = component
                        is_finished = False
                        break
                    end_date = max(end_date, component[WORK_END_DATE])
            if is_finished:
                current_node[WORK_END_DATE] = end_date
                current_node = get_parent_node(current_node)
            continue
        elif list(current_node.labels)[0].find("Task") != -1:
            components = graph.run(
                """
            MATCH (c)-[:FOLLOW]->(p)
            WHERE id(c) = $child_id
            RETURN p
            """,
                child_id=current_node.identity,
            ).data()
            if len(components) > 0:
                waiting_node, end_date = get_waiting_node(
                    [component["p"] for component in components]
                )
                if waiting_node is None:
                    current_node[WORK_START_DATE] = max(
                        end_date, current_node[WORK_START_DATE]
                    )
                    graph.push(current_node)
                    current_node = get_parent_node(current_node)
                else:
                    current_node = waiting_node
                    continue
            else:
                parent_node = get_parent_node(current_node)
                if parent_node is None:
                    pass  # TODO
                else:
                    if WORK_END_DATE not in dict(parent_node):
                        current_node = parent_node
                    else:

                        current_node[WORK_START_DATE] = 11111111111111

        elif list(current_node.labels)[0].find("Virtual") != -1:
            components = graph.run(
                """
            MATCH (c)-[:FOLLOW]->(p)
            WHERE id(c) = $child_id
            RETURN p
            """,
                child_id=current_node.identity,
            ).data()
            waiting_node, end_date = get_waiting_node(
                [component["p"] for component in components]
            )
            if waiting_node is None:
                current_node[WORK_START_DATE] = max(
                    end_date, current_node[WORK_START_DATE]
                )
                current_node[WORK_END_DATE] = current_node[WORK_START_DATE] + timedelta(
                    days=current_node[WORK_DURATION]
                )
                graph.push(current_node)
                current_node = get_parent_node(current_node)
            else:
                current_node = waiting_node
                continue


# 获取当前节点的上级节点
def get_parent_node(item_node):
    type_label = list(item_node.labels)[0]
    query = """
    MATCH (c)-[:FOLLOW]->(p)
    WHERE id(c) = $child_id
    RETURN p
    """
    if type_label == "IfcBuildingStorey":
        query = """
        MATCH (c)<-[:CONTAINS]-(p)
        WHERE id(c) = $child_id
        RETURN p
        """
    if type_label.find("Ifc") != -1:
        query = """
        MATCH (c)-[:for]->(p)
        WHERE id(c) = $child_id
        RETURN p
        """
    parent_nodes = graph.run(query, child_id=item_node.identity).data()
    if len(parent_nodes) > 0:
        return parent_nodes[0]["p"]
    return None


def calc_work(worker_cnt):
    # 查找出以 Task 开头的标签节点
    datas = graph.run(
        """
        MATCH (t)-[:FOR]->(c)
        WHERE any(label IN labels(t) WHERE label =~'Task.*')
        RETURN t,c,labels(t) AS taskLabels
    """
    ).data()
    # TODO 这部分内存占用高，后面修改
    tx = graph.begin()
    for data in datas:
        task_node = data["t"]
        task_label_type = list(task_node.labels)[0]
        workload = task_node[WORKLOAD]  # 工作量
        task_node[WORKER] = worker_cnt  # 工作人数
        if task_label_type.find("Maintain") != -1:
            task_node[WORK_DURATION] = workload
        else:
            task_node[WORK_DURATION] = workload / worker_cnt  # 工期
        tx.push(task_node)
    tx.commit()


# # 第一层
# storeys = graph.run(
#     """
# MATCH (s:IfcBuildingStorey)-[:FOLLOW]->(b:IfcBuilding {GlobalId:${build_id})
# RETURN s
# """,
#     build_id=building["GlobalId"],
# ).data()
# storey = storeys[0]["s"]
# # 获取这个楼层的所有构件
# components = graph.run(
#     """
#     MATCH (c)-[:PLACED_IN]->(s:IfcBuildingStorey {GlobalId:${storey_id})
#     RETURN c
#
#
#     MATCH (t)-[:FOR]->(c)-[:PLACED_IN]->(s:IfcBuildingStorey {GlobalId:'0drpI4_iH3$QGPHPzFf22S'}),(t) -[:FOLLOW]->(f)
# RETURN t,c,f
#
#     """,
#     storey_id=storey["GlobalId"],
# ).data()
# for component in components:
#     pass
# TODO 后面优化
# 计算构件时间
def calc_project_date2(project_date):
    # 查询所有的building
    buildings = graph.run(
        """
        MATCH (c:IfcBuilding) RETURN c
    """
    ).data()
    buildings = [building["c"] for building in buildings]
    for building in buildings:
        get_item_follow(building, project_date)


def get_item_follow(item_node, work_start_date):
    work_end_date = work_start_date
    # 是IFC构键
    if "type" in dict(item_node):
        # IFC的结束时间以最后的子构建日期为准
        ifc_type = item_node["type"]
        if ifc_type == "IfcBuilding":
            components = graph.run(
                """
            MATCH (c)<-[:CONTAINS]-(s)
            WHERE id(s) = $s_id
            RETURN c
            """,
                s_id=item_node.identity,
            ).data()
            components = [component["c"] for component in components]
            for component in components:
                component = get_item_follow(component, work_start_date)
                work_end_date = max(WORK_END_DATE, component[WORK_END_DATE])

        if ifc_type == "IfcBuildingStorey":
            components = graph.run(
                """
            MATCH (c)-[:FOLLOW]->(p)
            WHERE id(c) = $child_id
            RETURN p
            """,
                child_id=item_node.identity,
            ).data()
            # 二楼依赖一楼
            if len(components) > 0:
                components = [component["p"] for component in components]
                for component in components:
                    if WORK_END_DATE not in dict(component):
                        component = get_item_follow(component, work_start_date)
                    work_start_date = max(work_start_date, component[WORK_END_DATE])

            components = graph.run(
                """
                MATCH (c)-[:PLACED_IN]-> (s)
                WHERE id(s) = $parent_id
                RETURN c
                """,
                parent_id=item_node["GlobalId"],
            ).data()
            components = [component["c"] for component in components]
            for component in components:
                component = get_item_follow(component, work_start_date)
                work_end_date = max(work_end_date, component["work_end_date"])

        # 每个楼层的IFC构建
        if ifc_type.find("Ifc") != -1:
            components = graph.run(
                """
                MATCH (c)-[:FOLLOW]->(p)
                WHERE id(c) = $child_id
                RETURN p
                """,
                child_id=item_node.identity,
            ).data()
            if len(components) > 0:
                components = [component["p"] for component in components]
                for component in components:
                    if WORK_END_DATE not in dict(component):
                        component = get_item_follow(component, work_start_date)
                    work_start_date = max(work_start_date, component[WORK_END_DATE])

            components = graph.run(
                """
                MATCH (c)-[:FOR]-> (s})
                WHERE id(s) = $child_id
                RETURN c
                """,
                parent_id=item_node.identity,
            ).data()
            components = [component["c"] for component in components]
            for component in components:
                component = get_item_follow(component, work_start_date)
                work_end_date = max(work_end_date, component[WORK_END_DATE])

        item_node[WORK_START_DATE] = work_start_date
        item_node[WORK_END_DATE] = work_end_date
        graph.push(item_node)
    elif (
        list(item_node.labels)[0].find("Task") != -1
    ):  # 每个构建自己的工序：  钢筋、模板、水凝
        components = graph.run(
            """
        MATCH (c)-[:FOLLOW]->(p)
        WHERE id(c) = $child_id
        RETURN p
        """,
            child_id=item_node.identity,
        ).data()
        # 存在前置任务，那么当前任务的开始日期
        # 以前置任务的最后完成日期，或 work_start_date 最晚为开始
        if len(components) > 0:
            components = [component["p"] for component in components]
            for component in components:
                if WORK_END_DATE not in dict(component):
                    component = get_item_follow(component, work_start_date)
                work_start_date = max(work_start_date, component[WORK_END_DATE])
        work_end_date = work_start_date + timedelta(days=item_node[WORK_DURATION])

    elif list(item_node.labels)[0].find("Virtual") != -1:  # 如果是虚拟的构建
        components = graph.run(
            """
        MATCH (c)-[:FOLLOW]->(p)
        WHERE id(c) = $child_id
        RETURN p
        """,
            child_id=item_node.identity,
        ).data()
        if len(components) > 0:
            components = [component["p"] for component in components]
            for component in components:
                if WORK_END_DATE not in dict(component):
                    component = get_item_follow(component, work_start_date)
                work_start_date = max(work_start_date, component[WORK_END_DATE])
            work_end_date = work_start_date

    item_node[WORK_START_DATE] = work_start_date
    item_node[WORK_END_DATE] = work_end_date
    graph.push(item_node)
    return item_node
